/*
 * These functions use dialog module's callback framework to hook
 * functions to kamailio's dialog procedure. *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>

#include "dialogCallback.h"

#include "../../parser/parse_to.h"
#include "../../str.h"
#include "../dialog/dlg_load.h"
#include "../dialog/dlg_hash.h"
#include "QuantumSipServerNotifications.h"
#include "CurrentDialogTable.h"
#include "snmpstats_db.h"
#include "utilities.h"


struct dlg_binds dialog_api;

/*
 * Defined in CurrentDialogTable.c
 */
extern struct CurrentDialogTable_entry **snmpDataList;

struct CurrentDialogTable_cb_params {

    int tableIndex;

    struct CurrentDialogTable_entry **snmpDataList;

};


/****************************
 *   Function declaration
 ****************************/

void on_dialog_create(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params);

void on_dialog_update(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params);

void free_dialog_param(void *param);

void process_data(struct dlg_cell * dlg, int type, struct CurrentDialogTable_cb_params *cb_params);


/*
 *  Binds to the dialog API and hook callbck function to the dialog's creation callback.
 */
int init_dialog_callback() {

    struct CurrentDialogTable_cb_params *cb_params =
        (struct CurrentDialogTable_cb_params *)shm_malloc(sizeof(struct CurrentDialogTable_cb_params));

    cb_params->tableIndex = 1;
    cb_params->snmpDataList = snmpDataList;

    /* bind to the dialog API */
    if (0 != load_dlg_api(&dialog_api)) {
        LM_ERR("failed to load dialog API - is dialog module loaded?\n");
        return -1;
    }

    /* register dialog creation callback */
    if (0 != dialog_api.register_dlgcb(NULL, DLGCB_CREATED, on_dialog_create, cb_params, free_dialog_param)) {
        LM_ERR("Cannot register callback to dialog creation\n");
        return -1;
    }

    LM_INFO("Initializing dialog callbacks......Done.");
    return 0;
}

/*
 * This function will be invoked when a new INVITE dialog is created. It refers to the phase 1 of
 * a classic sip message flow where the INVITE message is received by sip server but not yet reaches
 * the called party(callee).
 * Once invoked, the function will register other interesting callbacks to this dialog.
 */
void on_dialog_create(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params) {

    LM_INFO("DIALOG created...CallId=%.*s", dlg->callid.len, dlg->callid.s);

    struct sip_msg *request = _params->req;
    if (request->REQ_METHOD != METHOD_INVITE)
        return;

    if (0 != load_dlg_api(&dialog_api)) { //Must reload dialog api here.
        LM_ERR("failed to load dialog API - is dialog module loaded?\n");
        return;
    }

    // Retreives the snmp list's pointer from parameters.
    //struct CurrentDialogTable_entry** snmpDataListHeadPointer = (struct CurrentDialogTable_entry **)(*_params->param);
    struct CurrentDialogTable_cb_params *cb_params = (struct CurrentDialogTable_cb_params *)(*_params->param);


    // register all per-dialog callbacks except for DLGCB_DESTROY
    if (0 != dialog_api.register_dlgcb(dlg,
                                       DLGCB_FAILED | DLGCB_CONFIRMED_NA | DLGCB_CONFIRMED | DLGCB_REQ_WITHIN | DLGCB_TERMINATED |
                                       DLGCB_EXPIRED | DLGCB_EARLY | DLGCB_RESPONSE_FWDED | DLGCB_RESPONSE_WITHIN  |
                                       DLGCB_MI_CONTEXT,
                                       on_dialog_update, cb_params, free_dialog_param)) {
        LM_ERR("Cannot register callbacks for per-dialog callbacks\n");
        return;
    }

    process_data(dlg, type, cb_params);
}

/*
 * This function handles all the dialog callbacks registered in on_dialog_create() function.
 * It updates the snmp list from CurrentDialogTable regarding different type of the dialog
 * and send out a snmp notification using the address imported in snmpstats module.
 */
void on_dialog_update(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params) {

    str tag;

    struct sip_msg *msg;

    LM_INFO("DIALOG callback received, from=[%.*s] to=[%.*s]\n", dlg->from_uri.len, dlg->from_uri.s, dlg->to_uri.len, dlg->to_uri.s);

    if (dlg->tag[0].len && dlg->tag[0].s ) {
        LM_DBG("dialog callback: tag[0] = %.*s", dlg->tag[0].len, dlg->tag[0].s);
    }

    if (dlg->tag[0].len && dlg->tag[1].s ) {
        LM_DBG("dialog callback: tag[1] = %.*s", dlg->tag[1].len, dlg->tag[1].s);
    }

    if (type != DLGCB_DESTROY) {
        msg = dlg_get_valid_msg(_params);
        if (!msg) {
            LM_ERR("no SIP message available in callback parameters\n");
            return;
        }

        /* get to tag*/
        if ( !msg->to) {
            // to header not defined, parse to header
            LM_DBG("to header not defined, parse to header\n");
            if (parse_headers(msg, HDR_TO_F,0)<0) {
                //parser error
                LM_ERR("parsing of to-header failed\n");
                tag.s = 0;
                tag.len = 0;
            } else if (!msg->to) {
                // to header still not defined
                LM_DBG("no to although to-header is parsed: bad reply or missing TO hdr :-/\n");
                tag.s = 0;
                tag.len = 0;
            } else
                tag = get_to(msg)->tag_value;
        } else {
            tag = get_to(msg)->tag_value;
            if (tag.s==0 || tag.len==0) {
                LM_DBG("missing TAG param in TO hdr :-/\n");
                tag.s = 0;
                tag.len = 0;
            }
        }
        if (tag.s) {
            LM_ERR("dialog callback: msg->to->parsed->tag_value = %.*s", tag.len, tag.s);
        }
    }

    // Retreives the snmp list's pointer from parameters.
    // struct CurrentDialogTable_entry** snmpDataListHeadPointer = (struct CurrentDialogTable_entry **)(*_params->param);
    struct CurrentDialogTable_cb_params *cb_params = (struct CurrentDialogTable_cb_params *)(*_params->param);
    process_data(dlg, type, cb_params);
}


/*
 * Free the param pointer.
 */
void free_dialog_param(void *param) {
    //shm_free(param);
    //LM_DBG("Freeing param...Done");
    return;
}

/*
 * This function retrieves interesting data from the dlg_cell struct, stores them for snmp get-requests
 * and sends them out via snmp notification.
 * Provoked on every dialog call back.
 */
//void process_data(struct CurrentDialogTable_entry **snmpDataList, struct dlg_cell *dlg, int type) {
void process_data(struct dlg_cell *dlg, int type, struct CurrentDialogTable_cb_params *cb_params) {

    LM_INFO("Processing data call back. Type=>");
    switch (type) {
    case DLGCB_CREATED:
        INFO("DLGCB_CREATED");
        break;
    case DLGCB_EARLY:
        INFO("DLGCB_EARLY");
        break;
    case DLGCB_FAILED:
        INFO("DLGCB_FAILED");
        break;
    case DLGCB_CONFIRMED_NA:
        INFO("DLGCB_CONFIRMED_NA");
        break;
    case DLGCB_CONFIRMED:
        INFO("DLGCB_CONFIRMED");
        break;
    case DLGCB_REQ_WITHIN:
        INFO("DLGCB_REQ_WITHIN");
        break;
    case DLGCB_TERMINATED:
        INFO("DLGCB_TERMINATED");
        break;
    case DLGCB_EXPIRED:
        INFO("DLGCB_EXPIRED");
        break;
    case DLGCB_RESPONSE_FWDED:
        INFO("DLGCB_RESPONSE_FWDED");
        break;
    case DLGCB_RESPONSE_WITHIN:
        INFO("DLGCB_RESPONSE_WITHIN");
        break;
    case DLGCB_MI_CONTEXT:
        INFO("DLGCB_MI_CONTEXT");
        break;
    case DLGCB_DESTROY:
        INFO("DLGCB_DESTROY");
        break;
    default:
        LM_ERR("dialog callback type 'unknown' received, from=%.*s\n", dlg->from_uri.len, dlg->from_uri.s);
    }

    // Processes only the types that interest us.
    if ( type == DLGCB_CREATED ||
            type == DLGCB_EARLY ||
            type == DLGCB_CONFIRMED_NA ||
            type == DLGCB_TERMINATED ||
            type == DLGCB_FAILED ||
            type == DLGCB_DESTROY) {

        // call id
        char* callId = malloc(dlg->callid.len * sizeof(char) + 1);
        strncpy(callId, dlg->callid.s, dlg->callid.len);
        callId[dlg->callid.len] = '\0';

        // Parse calleeId from 'to_uri'
        char temp[100];
        strncpy(temp, dlg->to_uri.s, dlg->to_uri.len);
        temp[dlg->to_uri.len] = '\0';

        // INFO("Parsing [%s]", temp);
        strtok(temp, ":");

        char *tmpString = strtok(NULL, "@");
        char *calleeId = malloc(strlen(tmpString) * sizeof(char) + 1);
        strcpy(calleeId, tmpString);
        calleeId[strlen(tmpString)] = '\0';

        // Parse other data from 'from_uri'
        memset(temp, 0, sizeof(temp));
        strncpy(temp, (const char*)(dlg->from_uri.s), dlg->from_uri.len);
        // INFO("Parsing [%s]", temp);

        // string should be like this sip:callerId#callerProxyId#calleeProxyId#param@serverAddress
        strtok(temp, ":");
        char *callerId = strtok(NULL, "#");
        char *callerProxyId = strtok(NULL, "#");

        char *calleeProxyId = strtok(NULL, "#");

        char callType[2];
        callType[1] = '\0';

        //In case couldn't parse caller or callee's qid
        if (callerProxyId == NULL) {
            callerProxyId = "";
            callType[0] = '0';
        }

        if (calleeProxyId == NULL) {
            calleeProxyId = "";
            callType[0] = '0';
        }

        if (callType[0] != '0') {
            // Parse call type
            char *callParam = strtok(NULL, "@");
            if (callParam && strlen(callParam) >= 5) {
                char rawType = callParam[4];
                if (rawType == '1' || rawType == '4') //audio or video
                    callType[0] = '1';
                else if (rawType == '3') // IM
                    callType[0] = '4';
                else
                    callType[0] = rawType;
            } else
                callType[0] = '0';
            callType[1] = '\0';
        }

        // extra data used as numeric id
        int hashEntry = dlg->h_entry;
        int hashId = dlg->h_id;

        // Call State
        char callState[2];
        callState[0] = '0';
        callState[1] = '\0';

        switch (type) {
        case DLGCB_CREATED:
            callState[0] = '1';
            break;
        case DLGCB_EARLY:
            callState[0] = '2';
            break;
        case DLGCB_FAILED:
            break;
        case DLGCB_CONFIRMED_NA:
            callState[0] = '3';
            break;
        case DLGCB_CONFIRMED:
            break;
        case DLGCB_REQ_WITHIN:
            break;
        case DLGCB_TERMINATED:
            callState[0] = '4';
            break;
        case DLGCB_EXPIRED:
            break;
        case DLGCB_RESPONSE_FWDED:
            break;
        case DLGCB_RESPONSE_WITHIN:
            break;
        case DLGCB_MI_CONTEXT:
            break;
        case DLGCB_DESTROY:
            break;
        default:
            LM_ERR("dialog callback type 'unknown' received, from=%.*s\n", dlg->from_uri.len, dlg->from_uri.s);
        }

        // In the case of a failed dialog, we query missed_calls TABLE in db to find out why it failed.
        // But the table is updated *AFTER* the DLGCB_FAILED callback. So it can only be queried on
        // DLGCB_DESTROY callback.
        if (type == DLGCB_DESTROY) {
            callState[0] = dialog_db_get_failure_type(callId);
        }

        /*
                LM_INFO("==== DATA RETRIEVED FROM process_data() ====");
                LM_INFO("hashEntry=%d hashId=%d CallId=%s", hashEntry, hashId,        callId);
                LM_INFO("Caller=%s@%s Callee=%s@%s",        callerId,  callerProxyId, calleeId, calleeProxyId);
                LM_INFO("CallType=%s CallState=%s",         callType,  callState);
                LM_INFO("********************************************");
        */
        // Pack up the dialog context
        CurrentDialogTable_context_str* dialog_context = pkg_malloc(sizeof(CurrentDialogTable_context_str));

        dialog_context->HashEntry = hashEntry;
        dialog_context->HashId    = hashId;

        safe_copy_string(dialog_context->CallId,          callId);
        safe_copy_string(dialog_context->CallerId,        callerId);
        safe_copy_string(dialog_context->CallerProxyId,   callerProxyId);
        safe_copy_string(dialog_context->CalleeId,        calleeId);
        safe_copy_string(dialog_context->CalleeProxyId,   calleeProxyId);
        safe_copy_string(dialog_context->CallType,        callType);
        safe_copy_string(dialog_context->CallState,       callState);

        time(&(dialog_context->timestamp));

        free(callId);
        free(calleeId);

        // Now we are ready to send them out...

        // SNMP Notification
        if (atoi(callType) == 0) {
            INFO("Number parsed is incorrect. SNMP notification skipped.");
            memset(temp, 0, sizeof(temp));
            strncpy(temp, (const char*)(dlg->from_uri.s), dlg->from_uri.len);
            INFO("String to parse is not good? [%s]", temp);
        } else if (type == DLGCB_FAILED) {
            // register DLGCB_DESTROY to send call state for failed dialog

            if (0 == load_dlg_api(&dialog_api) &&
                    0 != dialog_api.register_dlgcb(dlg, DLGCB_DESTROY,
                                                   on_dialog_update, cb_params, free_dialog_param)) {
                LM_ERR("Cannot register callbacks for DLGCB_DESTROY\n");
            }
            INFO("Call state not ready yet. SNMP notification postponed.");
        } else
            send_current_dialog_notification(dialog_context);

        // Persistence
        // CurrentDialogTable_db_add(dialog_context);

        // SNMP Container
        struct CurrentDialogTable_entry **theList = cb_params->snmpDataList;
        if (theList != NULL) {
            if ( type == DLGCB_CREATED ||
                    type == DLGCB_EARLY ||
                    type == DLGCB_CONFIRMED_NA )

                CurrentDialogTable_addOrUpdateEntry(theList, dialog_context);
            else
                // terminated successfully or failed
                CurrentDialogTable_removeEntry(theList, dialog_context->CallId);
        }

        //Frees the context
        pkg_free(dialog_context);

    } else { // ignore other types for now
        // LM_INFO("Dialog callback ignored.");
    }
}


